// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SerializationSurrogate.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Microsoft.Robotics.IO
{
    using System;
    using System.CodeDom;
    using System.Collections.ObjectModel;
    using System.Reflection;
    using System.Runtime.InteropServices;
    using System.Runtime.Serialization;

    /// <summary>
    /// Class that overrides the behavior of DataContractSerializer for arrays of simple types.
    /// </summary>
    public class SerializationSurrogate : IDataContractSurrogate
    {
        /// <summary>
        /// Determines whether to compress Arrays during serialization.
        /// </summary>
        private bool noCompression = false;

        /// <summary>
        /// Determines whether to use serializable arrays during serialization.
        /// </summary>
        private bool useSerializableArray = true;

        /// <summary>
        /// Initializes a new instance of the <see cref = "SerializationSurrogate"/> class.
        /// </summary>       
        /// <param name="noCompression">A parameter which indicates whether to compress arrays or not</param>
        /// <param name="useSerializableArray">A parameter which determines whether to user serializable arrays during serialization.</param>
        public SerializationSurrogate(bool noCompression, bool useSerializableArray = true)
        {
            this.noCompression = noCompression;
            this.useSerializableArray = useSerializableArray;
        }

        /// <summary>
        /// During serialization, deserialization, and schema import and export, returns a data contract type that substitutes the specified type. 
        /// </summary>
        /// <param name="type">The CLR type Type to substitute. </param>
        /// <returns>The Type to substitute for the type value. This type must be serializable by the DataContractSerializer. 
        /// For example, it must be marked with the DataContractAttribute attribute or other mechanisms that the serializer recognizes.</returns>
        public Type GetDataContractType(Type type)
        {
            if (type.IsArray)
            {
                if (this.useSerializableArray)
                {
                    Type elementType = type.GetElementType();
                    if (elementType.IsPrimitive)
                    {
                        return typeof(SerializableArray);
                    }
                    else if (elementType.IsValueType && (elementType.Attributes & TypeAttributes.SequentialLayout) != 0)
                    {
                        if (elementType.GetCustomAttributes(typeof(DataContractAttribute), false).Length == 0)
                        {
                            return typeof(SerializableArray<>).MakeGenericType(elementType);
                        }
                    }
                }
            }
            else if (type == typeof(Type))
            {
                return typeof(SerializableType);
            }

            return type;
        }

        /// <summary>
        /// During deserialization, returns an object that is a substitute for the specified object.
        /// </summary>
        /// <param name="obj">The deserialized object to be substituted.</param>
        /// <param name="targetType">The Type that the substituted object should be assigned to.</param>
        /// <returns>The substituted deserialized object. This object must be of a type that is serializable by the DataContractSerializer. 
        /// For example, it must be marked with the DataContractAttribute attribute or other mechanisms that the serializer recognizes.</returns>
        public object GetDeserializedObject(object obj, Type targetType)
        {
            if (obj is SerializableArray)
            {
                SerializableArray array = obj as SerializableArray;
                return array.GetTypedArray();
            }
            else if (obj is SerializableType)
            {
                SerializableType type = obj as SerializableType;
                return type.GetTypeFromName();
            }

            return obj;
        }

        /// <summary>
        /// During serialization, returns an object that substitutes the specified object. 
        /// </summary>
        /// <param name="obj">The object to substitute.</param>
        /// <param name="targetType">The Type that the substituted object should be assigned to.</param>
        /// <returns>The substituted object that will be serialized. The object must be serializable by the DataContractSerializer. 
        /// For example, it must be marked with the DataContractAttribute attribute or other mechanisms that the serializer recognizes.</returns>
        public object GetObjectToSerialize(object obj, Type targetType)
        {
            if (targetType == typeof(SerializableArray) || targetType.IsSubclassOf(typeof(SerializableArray)))
            {
                SerializableArray array = (SerializableArray)Activator.CreateInstance(targetType, new object[] { this.noCompression });
                array.PrepareToSerialize((Array)obj);
                return array;
            }
            else if (targetType == typeof(SerializableType))
            {
                return new SerializableType((Type)obj);
            }

            return obj;
        }

        /// <summary>
        /// Not used. 
        /// During schema export operations, inserts annotations into the schema for non-null return values. 
        /// </summary>
        /// <param name="clrType">The CLR type to be replaced</param>
        /// <param name="dataContractType">The data contract type to be annotated.</param>
        /// <returns>An object that represents the annotation to be inserted into the XML schema definition. </returns>
        public object GetCustomDataToExport(Type clrType, Type dataContractType)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Not used.
        /// During schema export operations, inserts annotations into the schema for non-null return values. 
        /// </summary>
        /// <param name="memberInfo">A MemberInfo that describes the member.</param>
        /// <param name="dataContractType">A Type.</param>
        /// <returns>An object that represents the annotation to be inserted into the XML schema definition.</returns>
        public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Not used.
        /// Sets the collection of known types to use for serialization and deserialization of the custom data objects.
        /// </summary>
        /// <param name="customDataTypes">A Collection(T) of Type to add known types to.</param>
        public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Not used.
        /// During schema import, returns the type referenced by the schema.
        /// </summary>
        /// <param name="typeName">The name of the type in schema.</param>
        /// <param name="typeNamespace">The namespace of the type in schema.</param>
        /// <param name="customData">The object that represents the annotation inserted into the XML schema definition, which is data that can be used for finding the referenced type.</param>
        /// <returns>The Type to use for the referenced type.</returns>
        public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Not used.
        /// Processes the type that has been generated from the imported schema.
        /// </summary>
        /// <param name="typeDeclaration">A CodeTypeDeclaration to process that represents the type declaration generated during schema import.</param>
        /// <param name="compileUnit">The CodeCompileUnit that contains the other code generated during schema import.</param>
        /// <returns>A CodeTypeDeclaration that contains the processed type.</returns>
        public CodeTypeDeclaration ProcessImportedType(CodeTypeDeclaration typeDeclaration, CodeCompileUnit compileUnit)
        {
            throw new NotImplementedException();
        }
    }
}
